import 'package:code_builder/code_builder.dart';
import 'package:dart_style/dart_style.dart';

import 'ast.dart';

abstract class Gen {
  final Map<String, Option> options = {};
  final GenSettings settings = GenSettings()..yy = 'yy';
  final Grammar grammar;

  Gen(this.grammar) {
    for (var option in grammar.optionsSection.options) {
      if (option.name == 'option') {
        if (option.values.isEmpty) {
          throw "Missing value for '%option'.";
        } else if (option.typeAttr != null) {
          throw "Cannot specify <" +
              option.typeAttr +
              "> attribute for option '${option.name}'.";
        } else if (option.values[0].dartCode != null) {
          throw "Option name must be an identifier, not '{" +
              option.values[0].dartCode.text +
              "}'.";
        } else if (option.values[0].string == null) {
          throw "Option name must be an identifier, not '" +
              option.values[0].string +
              "'.";
        }
        options[option.values[0].reference] = option;
      } else if (!const ['token', 'type'].contains(option.name)) {
        throw "Unsupported directive '${option.name}'.";
      }
    }
  }

  Library build();

  void parseCommonOptions() {
    if (options.containsKey('prefix')) {
      var prefix = options['prefix'];
      if (prefix.values.length <= 1) {
        throw "Missing value for '%option prefix'.";
      }
      if (prefix.values[1].dartCode != null) {
        throw "Value for '%option prefix' must be an identifier.";
      }
      settings.yy = prefix.values[1].reference;
    }
  }

  String buildToString() {
    return DartFormatter().format(build().accept(DartEmitter()).toString());
  }
}

class GenSettings {
  String yy;

  String get private => '_$yy';

  String get yyin => '${yy}in';

  String get yylex => '${yy}lex';

  String get yylval => '${yy}lval';

  String get yystack => '_${yy}stack';

  String get yytext => '${yy}text';

  String get yytok => '${yy}tok';

  String get yytokType => '${yy}Tok';

  String get yyval => '${yy}val';

  String yystype = 'int'; // TODO: This should default to a yytoken type.

  String lexMethod(int i) => '${private}_lex_${i}';

  String parseMethod(String s) => '${private}_parse_$s';

  void enableYystype() {
    yystype = 'YYSTYPE';
  }
}
